package org.sergiy.metawidget.rules;

import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;

/**
 * A view definition for a domain object used by rules
 * @author Sergiy Litsenko
 *
 */
public class ViewDefinition implements Serializable {
	private static final long serialVersionUID = -5759111446846597839L;

	// domain object
	private Object domainObject;
	
	// domain object view properties map: property name, property attributes (attribute name, attribute value)
	private Map<String, Map<String,String>> properties = new HashMap<String, Map<String,String>>(); 

	// domain object view actions map: action name, property attributes (attribute name, attribute value)
	private Map<String, Map<String,String>> actions = new HashMap<String, Map<String,String>>(); 
	
	// view name used as context for the domain object (e.g. JSF viewId).
	private String viewName;

	// the inspection state if true then no more business rules applied
	private boolean inspected;
	
	/**
	 *  Default constructor 
	 */
	public ViewDefinition() {
	}

	/**
	 *  Constructor 
	 */
	public ViewDefinition(Object domainObject) {
		setDomainObject(domainObject);
	}

	/**
	 *  Constructor 
	 */
	public ViewDefinition(Object domainObject, String viewName) {
		setDomainObject(domainObject);
		setViewName(viewName);
	}
	
	/**
	 * @return the domainObject
	 */
	public Object getDomainObject() {
		return domainObject;
	}

	/**
	 * @param domainObject the domainObject to set
	 */
	public void setDomainObject(Object domainObject) {
		this.domainObject = domainObject;
		// mark as not inspected yet
		setInspected(false);
	}

	/**
	 * @return the domainObject class name
	 */
	public String getDomainObjectClassName() {
		return domainObject != null? domainObject.getClass().getName(): null;
	}

	/**
	 * Get domain object view properties and they attributes
	 * @return the properties and they attributes
	 */
	public Map<String, Map<String, String>> getProperties() {
		return properties;
	}

	/**
	 * Get property attributes by name
	 * @param propertyName the property name
	 * @return property attributes by name
	 */
	public Map<String, String> getPropertyAttributes(String propertyName) {
		// check for null
		if (propertyName == null || propertyName.trim().isEmpty()) {
			return null;
		}
		// get if exist
		Map<String, String> attributes = properties.get(propertyName);
		// if not exists then create empty map
		if (attributes == null) {
			attributes = new HashMap<String,String>();
			properties.put(propertyName, attributes);
		}
		// return
		return attributes;
	}
	
	/**
	 * Get single attribute by property name and attribute name 
	 * @param propertyName the property name
	 * @param attribute the attribute name
	 * @return the attribute value or null
	 */
	public String getPropertyAttribute(String propertyName, String attribute) {
		// get all attributes
		Map<String, String> attributes = getPropertyAttributes(propertyName);
		// return
		return attributes != null? attributes.get(attribute) : null; 
	}
	
	/**
	 * Set single attribute by property name and attribute name
	 * @param propertyName the property name
	 * @param attribute the attribute name
	 * @param value the attribute value 
	 */
	public void setPropertyAttribute(String propertyName, String attribute, String value) {
		// get all attributes
		Map<String, String> attributes = getPropertyAttributes(propertyName);
		if (attributes != null && attribute != null) {
			if (value != null) {
				attributes.put(attribute, value); 
			} else {
				attributes.remove(attribute);
			}
		}
	}

	
	/**
	 * Get domain object view actions and they attributes
	 * @return the actions and they attributes
	 */
	public Map<String, Map<String, String>> getActions() {
		return actions;
	}

	/**
	 * Get action attributes by name
	 * @param actionName the property name
	 * @return action attributes by name
	 */
	public Map<String, String> getActionAttributes(String actionName) {
		// check for null
		if (actionName == null || actionName.trim().isEmpty()) {
			return null;
		}
		// get if exist
		Map<String, String> attributes = actions.get(actionName);
		// if not exists then create empty map
		if (attributes == null) {
			attributes = new HashMap<String,String>();
			actions.put(actionName, attributes);
		}
		// return
		return attributes;
	}
	
	/**
	 * Get single attribute by action name and attribute name 
	 * @param actionName the action name
	 * @param attribute the attribute name
	 * @return the attribute value or null
	 */
	public String getActionAttribute(String actionName, String attribute) {
		// get all attributes
		Map<String, String> attributes = getActionAttributes(actionName);
		// return
		return attributes != null? attributes.get(attribute) : null; 
	}
	
	/**
	 * Set single attribute by action name and attribute name
	 * @param actionName the action name
	 * @param attribute the attribute name
	 * @param value the attribute value 
	 */
	public void setActionAttribute(String actionName, String attribute, String value) {
		// get all attributes
		Map<String, String> attributes = getActionAttributes(actionName);
		if (attributes != null && attribute != null) {
			if (value != null) {
				attributes.put(attribute, value); 
			} else {
				attributes.remove(attribute);
			}
		}
	}
	
	
	/**
	 * @return the viewName
	 */
	public String getViewName() {
		return viewName;
	}

	/**
	 * @param viewName the viewName to set
	 */
	public void setViewName(String viewName) {
		this.viewName = viewName;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return String.format(
				"ViewDefinition [domainObject=%s, viewName=%s, properties=%s, actions=%s, inspected=%b]",
				domainObject, viewName, properties, actions, inspected);
	}

	/**
	 * True if domain object has been inspected
	 * @return the inspected
	 */
	public boolean isInspected() {
		return inspected;
	}

	/**
	 * Set inspection state
	 * @param inspected the inspected to set
	 */
	public void setInspected(boolean inspected) {
		this.inspected = inspected;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#hashCode()
	 */
	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((actions == null) ? 0 : actions.hashCode());
		result = prime * result
				+ ((domainObject == null) ? 0 : domainObject.hashCode());
		result = prime * result + (inspected ? 1231 : 1237);
		result = prime * result
				+ ((properties == null) ? 0 : properties.hashCode());
		result = prime * result
				+ ((viewName == null) ? 0 : viewName.hashCode());
		return result;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#equals(java.lang.Object)
	 */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (!(obj instanceof ViewDefinition)) {
			return false;
		}
		ViewDefinition other = (ViewDefinition) obj;
		if (actions == null) {
			if (other.actions != null) {
				return false;
			}
		} else if (!actions.equals(other.actions)) {
			return false;
		}
		if (domainObject == null) {
			if (other.domainObject != null) {
				return false;
			}
		} else if (!domainObject.equals(other.domainObject)) {
			return false;
		}
		if (inspected != other.inspected) {
			return false;
		}
		if (properties == null) {
			if (other.properties != null) {
				return false;
			}
		} else if (!properties.equals(other.properties)) {
			return false;
		}
		if (viewName == null) {
			if (other.viewName != null) {
				return false;
			}
		} else if (!viewName.equals(other.viewName)) {
			return false;
		}
		return true;
	}
}
